using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Transactions;
using Core;
using NHibernate;
using NHibernate.Criterion;
using NHibernate.Linq;
using Expression = NHibernate.Criterion.Expression;

namespace IglooCoder.Commons.WcfNhibernate
{
    public class BaseNHibernateRepository<T> //where T : IDomain
    {
        private readonly ISessionStorage _sessionStorage;
        public TransactionScope Transaction { get; set; }
        public long RecordCount { get; set; }
        private ISession _session;

        public BaseNHibernateRepository(ISessionStorage sessionStorage)
        {
            _sessionStorage = sessionStorage;
        }

        public BaseNHibernateRepository(ISessionStorage sessionStorage,ISession session)
        {
            _sessionStorage = sessionStorage;
            _session = session;
        }

        public ISession Session
        {
            get { return _session == null ? _sessionStorage.GetSession() : _session; }
        }

        public T FetchById(Object idToSearchFor)
        {
            T Result = default(T); 
            Result = Session.Get<T>(idToSearchFor);
            return Result; 
        }

        public IList<T> FetchAll()
        {
            IList<T> Result = default(IList<T>);
            using (var tx = Session.BeginTransaction())
            {
                Result = Session.CreateCriteria(typeof (T)).List<T>();
                tx.Commit();
            }
            return Result;
        }

        public virtual void SaveOrUpdate(T classToUpdate)
        {
          Session.SaveOrUpdate(classToUpdate);
        }

        public virtual void Save(T classToUpdate)
        {
           Session.Save(classToUpdate);
        }

        public void Update(T classToUpdate)
        {
           Session.Update(classToUpdate);
              
        }

        public virtual void Delete(T classToDelete)
        {
            Session.Delete(classToDelete);
        }


        public T GetDataOne(Expression<Func<T,bool>> Criteria)
        {
            T Result = default(T);

            //if (! Session.Transaction.IsActive)
            //{
            //    using (var tx = Session.BeginTransaction())
            //    {
            //        Result = Session.Linq<T>().FirstOrDefault(Criteria);
            //        tx.Commit();
            //    }
            //}else
            //{
                Result = Session.Linq<T>().FirstOrDefault(Criteria);
            //}
            
            
            return Result;
        }


        public virtual IList<T> GetDataList(Expression<Func<T, bool>> Criteria)
        {
            IList<T> Result = new List<T>();
            //using (var tx = Session.BeginTransaction())
            //{
                Result = Session.Linq<T>().Where(Criteria).ToList();
            //    tx.Commit();
            //}

            return Result;
        }

        public virtual IList<T> GetDataPaged(int page, int pageSize)
        {

            IList<T> Results;
            using (var tx = Session.BeginTransaction())
            {
                //var query = Session.Linq<T>();
                //Results = query.ToPagedList(page, pageSize);
                var criteria = Session.CreateCriteria(typeof (T)).SetFirstResult((page - 1)*pageSize).SetMaxResults(pageSize);
                var criteriaCount = Session.CreateCriteria(typeof (T)).SetProjection(Projections.RowCountInt64()).FutureValue<Int64>();
                
                Results = criteria.Future<T>().ToList();
                RecordCount = criteriaCount.Value;
                tx.Commit();
            }
            
            return Results;
        }



        public IList<T> GetDataPaged(Expression<Func<T, bool>> criteria, int page, int pageSize)
        {

            PagedList<T> Results;
            using (var tx = Session.BeginTransaction())
            {
                Results = Session.Linq<T>().Where(criteria).ToPagedList(page, pageSize);
                tx.Commit();
            }

            RecordCount = Results.TotalCount;
            return Results;
        }

        public IList<T> GetDataPaged(Expression<Func<T, bool>> criteria, Expression<Func<T, object>> orderby, int page, int pageSize)
        {

            PagedList<T> Results;
            using (var tx = Session.BeginTransaction())
            {
                Results = Session.Linq<T>().Where(criteria).OrderBy(orderby).ToPagedList(page, pageSize);
                tx.Commit();
            }
            RecordCount = Results.TotalCount;
            return Results;
        }


        public virtual IList<T> GetDataPaged(int page, int pageSize, string sortProperty, string sortType)
        {

            IList<T> Results;
            ICriteria criteria = null;
            using (var tx = Session.BeginTransaction())
            {
                if (sortType.ToUpper()=="ASC")
                {
                    //Results = Session.Linq<T>().ApplyOrder(sortProperty,"OrderBy").ToPagedList(page, pageSize);  

                    criteria =
                    Session.CreateCriteria(typeof (T)).AddOrder(new Order(sortProperty,true)).SetFirstResult((page - 1)*pageSize).SetMaxResults(pageSize);

                }else
                {
                    //Results = Session.Linq<T>().ApplyOrder(sortProperty, "OrderByDescending").ToPagedList(page, pageSize); 
                    criteria =
                    Session.CreateCriteria(typeof (T)).AddOrder(new Order(sortProperty,false)).SetFirstResult((page - 1)*pageSize).SetMaxResults(pageSize);

                }

               
                var criteriaCount = Session.CreateCriteria(typeof (T)).SetProjection(Projections.RowCountInt64()).FutureValue<Int64>();

                Results = criteria.Future<T>().ToList();
                RecordCount = criteriaCount.Value;

                tx.Commit();
            }

            //RecordCount = Results.TotalCount;
            return Results;
        }

        public virtual IList<T> GetDataPaged(Dictionary<String, Object> criteria,string expression, int page, int pageSize)
        {

            IList<T> Results = new List<T>();
            using (var tx = Session.BeginTransaction())
            {
                StringBuilder sb = new StringBuilder("from " + typeof(T).Name +  " i ");
                StringBuilder sb2 = new StringBuilder();
                
               
                int i = 0;

                var filter = new List<string>();
                foreach (var criterion in criteria)
                    if (! typeof(T).GetProperties().Any(x=>x.Name == criterion.Key)) filter.Add(criterion.Key);

                List<string> fieldDates = new List<string>();
                foreach (var key in criteria.Keys)
                {
                    DateTime tgl;
                    var value = criteria[key];
                    bool tes = DateTime.TryParse(value.ToString(),new CultureInfo("ID-id"),DateTimeStyles.None, out tgl);
                    if (tes) fieldDates.Add(key);
                }

                foreach (var fieldDate in fieldDates)
                {
                    criteria[fieldDate] = DateTime.Parse(criteria[fieldDate].ToString(),new CultureInfo("ID-id")).ToString("yyyy-MM-dd");
                }


                filter.ForEach((x)=>
                                   {
                                       criteria.Remove(x); 
                                   }
                    );

                if (criteria.Count > 0) sb2.Append(" Where ");

                foreach (var criterion in criteria)
                {
                    if (criterion.Value == null) continue;
                    if (criterion.Value.GetType() == typeof (string) && ! fieldDates.Any(x=>x == criterion.Key))
                        sb2.AppendFormat(" i." + criterion.Key + " like'%{0}%' ", criterion.Value.ToString());
                    else
                        sb2.AppendFormat(" i." + criterion.Key + "='{0}' ", criterion.Value);

                    i += 1;
                    if (i < criteria.Count)
                        sb2.Append(expression);
                }

                var Query1 = Session.CreateQuery(sb.ToString() + sb2.ToString());
                Query1.SetFirstResult((page-1)*pageSize);
                Query1.SetMaxResults(pageSize);

                var Query = Session.CreateMultiQuery().Add(Query1)
                    .Add(Session.CreateQuery("Select Count(*) from " + typeof(T).Name + " i " + sb2.ToString()));
                var res = Query.List();

                foreach (var re in res[0] as IList)
                    Results.Add((T) re);

                RecordCount = (long)(res[1] as IList)[0];
                tx.Commit();
            }

            return Results;
        }

        public virtual IList<T> GetDataList(Expression<Func<T, bool>> Criteria, Func<T, Object> order1, Func<T, Object> order2)
        {
            IList<T> Result = new List<T>();
            //using (var tx = Session.BeginTransaction())
            //{
            Result = Session.Linq<T>().Where(Criteria).OrderBy(order1).ThenBy(order2).ToList();
            //    tx.Commit();
            //}

            return Result;
        }


        public virtual IList<T> GetDataSearchPaged(Dictionary<String, Object> criteria, List<string> Operators, int page, int pageSize)
        {

            IList<T> Results = new List<T>();
            using (var tx = Session.BeginTransaction())
            {
                StringBuilder sb = new StringBuilder("from " + typeof(T).Name + " i ");
                StringBuilder sb2 = new StringBuilder();


                int i = 0;

                var filter = new List<string>();
                foreach (var criterion in criteria)
                    if (!typeof(T).GetProperties().Any(x => x.Name == criterion.Key)) filter.Add(criterion.Key);

                filter.ForEach((x) =>
                {
                    criteria.Remove(x);
                }
                    );

                if (criteria.Count > 0) sb2.Append(" Where ");


                foreach (var criterion in criteria)
                {
                    if (criterion.Value == null) continue;
                    if (criterion.Value.GetType() == typeof(string))
                        sb2.AppendFormat(" i." + criterion.Key + " {1} '{0}' ", criterion.Value.ToString(), Operators[i]);
                    else
                        sb2.AppendFormat(" i." + criterion.Key + "{1} '{0}' ", criterion.Value,Operators[i]);

                    i += 1;

                    if (i < criteria.Count)
                        sb2.Append(" AND ");
                }

                var Query1 = Session.CreateQuery(sb.ToString() + sb2.ToString());
                Query1.SetFirstResult((page - 1) * pageSize);
                Query1.SetMaxResults(pageSize);

                var Query = Session.CreateMultiQuery().Add(Query1)
                    .Add(Session.CreateQuery("Select Count(*) from " + typeof(T).Name + " i " + sb2.ToString()));
                var res = Query.List();

                foreach (var re in res[0] as IList)
                    Results.Add((T)re);

                RecordCount = (long)(res[1] as IList)[0];
                tx.Commit();
            }

            return Results;
        }


        public virtual void DeleteDetail(dynamic entityDetail,Object id)
        {
            var detail = Session.Get(entityDetail.GetType(),id);
            Session.Delete(detail);
        }

        public virtual void ExecuteTransaction(Action UnitofWork)
        {
                using (var scope = new TransactionScope())
                {
                    if (UnitofWork != null)
                    {
                        try
                        {
                            UnitofWork();
                        }
                        catch (Exception ex)
                        {
                            throw ex; 
                        }
                    }

                    scope.Complete();
               
            }
          
        }


        public virtual void ExecuteTransactionNative(Action UnitofWork)
        {
            using (var tx = Session.BeginTransaction())
            {
                if (UnitofWork != null)
                {
                    try
                    {
                        UnitofWork();
                    }
                    catch (Exception ex)
                    {
                        throw ex;
                    }
                }

                tx.Commit();
            }

        }

        public void Dispose()
        {
            Session.Transaction.Commit();
            if (Session != null)
                Session.Dispose();
        }

    
    }
}